home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue41 / System / UCAniIcon.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  1998-12-06  |  11.8 KB  |  358 lines

  1. unit UCAniIcon;
  2.  
  3. interface
  4.  
  5. uses Windows, SysUtils, Consts, Classes, Graphics;
  6.  
  7. type
  8.     TAniIconHeader = record
  9.         dwSizeof: LongInt;
  10.         dwFrames: LongInt;
  11.         dwSteps: LongInt;
  12.         dwCX: LongInt;                    { use this to store icon width }
  13.         dwCY: LongInt;                    { use this to store icon height }
  14.         dwBitCount: LongInt;
  15.         dwPlanes: LongInt;
  16.         dwJIFRate: LongInt;
  17.         dwFlags: LongInt;
  18.     end;
  19.  
  20.     TAniIcon = class (TGraphic)
  21.     private
  22.         Rates: TList;                     { Optional JIFRate info for each step }
  23.         FrameOffsets: TList;              { Stream offsets into each frame }
  24.         SequenceMap: TList;               { Optional frame sequence mapping }
  25.         Image: TMemoryStream;             { Memory Image of entire .ANI file }
  26.         fAuthor: String;                  { Optional author information }
  27.         fTitle: String;                   { Optional title information }
  28.         fHeader: TAniIconHeader;          { ANI header extracted from file }
  29.         fCurrentJIFs: Integer;            { current JIF count for this step }
  30.         fCurrentStep: Integer;            { current step number }
  31.         fCurrentFrame: Integer;           { currently displaying frame number }
  32.         fCurrentIcon: hIcon;              { currently displaying icon }
  33.         fTransparent: Boolean;            { for transparent blitting }
  34.         fBackColor: TColor;               { background color when not transparent }
  35.         procedure Clear;
  36.         procedure SetFrame (Index: Integer);
  37.     public
  38.         constructor Create; override;
  39.         destructor Destroy; override;
  40.         procedure Assign (Source: TPersistent); override;
  41.         procedure LoadFromStream (Stream: TStream); override;
  42.         procedure SaveToStream (Stream: TStream); override;
  43.         procedure Animate;
  44.         procedure LoadFromClipboardFormat (AFormat: Word; AData: THandle; APalette: HPalette); override;
  45.         procedure SaveToClipboardFormat (var Format: Word; var Data: THandle; var APalette: HPalette); override;
  46.         procedure Draw (ACanvas: TCanvas; const Rect: TRect); override;
  47.         property Author: String read fAuthor;
  48.         property Title: String read fTitle;
  49.         property Icon: hIcon read fCurrentIcon;
  50.         property Transparent: Boolean read fTransparent write fTransparent default False;
  51.         property BackgroundColor: TColor read fBackColor write fBackColor default clBtnFace;
  52.     protected
  53.         function GetEmpty: Boolean; override;
  54.         function GetHeight: Integer; override;
  55.         function GetWidth: Integer; override;
  56.         procedure SetHeight (Value: Integer); override;
  57.         procedure SetWidth (Value: Integer); override;
  58.     end;
  59.  
  60. implementation
  61.  
  62. { TAniIcon }
  63.  
  64. constructor TAniIcon.Create;
  65. begin
  66.     Inherited Create;
  67.     fTransparent := False;
  68.     fBackColor := clBtnFace;
  69.     Rates := TList.Create;
  70.     FrameOffsets := TList.Create;
  71.     SequenceMap := TList.Create;
  72.     Image := TMemoryStream.Create;
  73. end;
  74.  
  75. destructor TAniIcon.Destroy;
  76. begin
  77.     Clear;
  78.     Image.Free;
  79.     Rates.Free;
  80.     FrameOffsets.Free;
  81.     SequenceMap.Free;
  82.     Inherited Destroy;
  83. end;
  84.  
  85. procedure TAniIcon.Clear;
  86. begin
  87.     fAuthor := '--unavailable--';
  88.     fTitle := '--unavailable--';
  89.     Image.Clear;
  90.     Rates.Clear;
  91.     FrameOffsets.Clear;
  92.     SequenceMap.Clear;
  93.     if fCurrentIcon <> 0 then DestroyIcon (fCurrentIcon);
  94.     fCurrentIcon := 0;
  95. end;
  96.  
  97. procedure TAniIcon.Assign (Source: TPersistent);
  98. begin
  99.     if Source = Nil then Clear
  100.     else if Source is TAniIcon then LoadFromStream (TAniIcon (Source).Image)
  101.     else Inherited Assign (Source);
  102. end;
  103.  
  104. function TAniIcon.GetEmpty: Boolean;
  105. begin
  106.     Result := FrameOffsets.Count = 0;
  107. end;
  108.  
  109. procedure TAniIcon.SetHeight (Value: Integer);
  110. begin
  111.     raise EInvalidGraphicOperation.Create (sChangeIconSize);
  112. end;
  113.  
  114. procedure TAniIcon.SetWidth (Value: Integer);
  115. begin
  116.     raise EInvalidGraphicOperation.Create (sChangeIconSize);
  117. end;
  118.  
  119. function TAniIcon.GetWidth: Integer;
  120. begin
  121.     Result := fHeader.dwCX;
  122. end;
  123.  
  124. function TAniIcon.GetHeight: Integer;
  125. begin
  126.     Result := fHeader.dwCY;
  127. end;
  128.  
  129. procedure TAniIcon.LoadFromClipboardFormat (AFormat: Word; AData: THandle; APalette: HPalette);
  130. begin
  131.     raise EInvalidGraphicOperation.Create (sIconToClipboard);
  132. end;
  133.  
  134. procedure TAniIcon.SaveToClipboardFormat (var Format: Word; var Data: THandle; var APalette: HPalette);
  135. begin
  136.     raise EInvalidGraphicOperation.Create (sIconToClipboard);
  137. end;
  138.  
  139. procedure TAniIcon.LoadFromStream (Stream: TStream);
  140. const
  141.     sig_RIFF = $46464952;         { RIFF header                         }
  142.     sig_ACON = $4E4F4341;         { ACON form type                      }
  143.     sig_LIST = $5453494C;         { LIST sub-chunk                      }
  144.     sig_INFO = $4F464E49;         { INFO sub-chunk                      }
  145.     sig_INAM = $4D414E49;         { INAM - title information            }
  146.     sig_IART = $54524149;         { IART - author information           }
  147.     sig_anih = $68696E61;         { anih - header information           }
  148.     sig_rate = $65746172;         { optional JIF rates sub-chunk        }
  149.     sig_fram = $6D617266;         { fram - list of icon frames          }
  150.     sig_icon = $6E6F6369;         { icon - start of actual frame        }
  151.     sig_seq  = $20716573;         { seq - optional sequence information }
  152.  
  153. var
  154.     ChunkLen: LongInt;
  155.     EncounteredHeader: Boolean;
  156.  
  157.     procedure InvalidFile;
  158.     begin
  159.         raise EInvalidGraphic.Create ('Animated icon image is not valid');
  160.     end;
  161.  
  162.     function ReadByte: Byte;
  163.     begin
  164.         Image.ReadBuffer (Result, sizeof (Result));
  165.     end;
  166.  
  167.     function ReadLong: LongInt;
  168.     begin
  169.         Image.ReadBuffer (Result, sizeof (Result));
  170.     end;
  171.  
  172.     function ReadString: String;
  173.     var
  174.         p: PChar;
  175.         Len: LongInt;
  176.     begin
  177.         Len := ReadLong;
  178.         if (Len and 1) <> 0 then Inc (Len);
  179.         GetMem (p, Len + 1);
  180.         p[Len] := #0;
  181.         Image.ReadBuffer (p^, Len);
  182.         Result := StrPas (p);
  183.         FreeMem (p, Len + 1);
  184.     end;
  185.  
  186.     { Process an optional info header sub-chunk. Contains Title/Author }
  187.     procedure ParseTitleAuthor;
  188.     var
  189.         ChunkEnd: LongInt;
  190.     begin
  191.         ChunkEnd := ReadLong;
  192.         Inc (ChunkEnd, Image.Position);
  193.         if ReadLong <> sig_INFO then InvalidFile;
  194.  
  195.         while Image.Position < ChunkEnd do
  196.             case ReadLong of
  197.                 sig_INAM: fTitle := ReadString;
  198.                 sig_IART: fAuthor := ReadString;
  199.             end;
  200.     end;
  201.  
  202.     { Parse ANI header information }
  203.     procedure ParseAniHeader;
  204.     begin
  205.         if ReadLong <> sizeof (fHeader) then InvalidFile;
  206.         Image.ReadBuffer (fHeader, sizeof (fHeader));
  207.         EncounteredHeader := True;
  208.     end;
  209.  
  210.     { Parse optional JIFRates chunk OR }
  211.     {       optional Sequence Map      }
  212.     procedure ParseList (List: TList);
  213.     var
  214.         Len: LongInt;
  215.     begin
  216.         Len := ReadLong div sizeof (LongInt);
  217.         if Len <> fHeader.dwSteps then InvalidFile;
  218.         while Len > 0 do begin
  219.             List.Add (Pointer (ReadLong));
  220.             Dec (Len);
  221.         end;
  222.     end;
  223.  
  224.     { Parse the actual icon data itself }
  225.     procedure ParseIconList;
  226.     var
  227.         Idx: Integer;
  228.         Len, Next: LongInt;
  229.     begin
  230.         ReadLong; { Discard chunk length }
  231.         if ReadLong <> sig_fram then InvalidFile;
  232.         { Store frame offsets for later consumption }
  233.         for Idx := 0 to fHeader.dwFrames - 1 do begin
  234.             if ReadLong <> sig_icon then InvalidFile;
  235.             { Save position from beginning of length dword }
  236.             FrameOffsets.Add (Pointer (Image.Position));
  237.             { Read Length of this frame }
  238.             Len := ReadLong;
  239.             Next := Len + Image.Position;
  240.             { Dig a little deeper to get the icon size info }
  241.             if Idx = 0 then begin
  242.                 Image.Position := Image.Position + 6;
  243.                 fHeader.dwCX := ReadByte;
  244.                 fHeader.dwCY := ReadByte;
  245.             end;
  246.  
  247.             Image.Position := Next;
  248.         end;
  249.     end;
  250.  
  251. begin { LoadFromStream }
  252.     Clear;
  253.     Image.LoadFromStream (Stream);
  254.     EncounteredHeader := False;
  255.     { Validate initial eight-byte header }
  256.     { Note: Some .ANI files have filesize > header (e.g. appstart.ani) }
  257.     if (ReadLong <> sig_RIFF) or (ReadLong > Image.Size) then InvalidFile;
  258.     { Next item must be an ACON chunk }
  259.     if ReadLong <> sig_ACON then InvalidFile;
  260.  
  261.     while Image.Position < Image.Size do
  262.         { Case out on the sub-chunk we find }
  263.         case ReadLong of
  264.             sig_LIST: if not EncounteredHeader then ParseTitleAuthor else ParseIconList;
  265.             sig_anih: ParseAniHeader;
  266.             sig_rate: ParseList (Rates);
  267.             sig_seq:  ParseList (SequenceMap);
  268.  
  269.             else      begin { Unknown chunk - just skip it }
  270.                           ChunkLen := ReadLong;
  271.                           Image.Position := Image.Position + ChunkLen;
  272.                       end;
  273.         end;
  274.  
  275.     SetFrame (0);
  276. end;
  277.  
  278. procedure TAniIcon.SaveToStream (Stream: TStream);
  279. begin
  280.     if GetEmpty then raise EInvalidGraphicOperation.Create (sInvalidImage);
  281.     with Image do Stream.WriteBuffer (Memory^, Size);
  282. end;
  283.  
  284. procedure TAniIcon.Draw (ACanvas: TCanvas; const Rect: TRect);
  285. var
  286.     bm: TBitmap;
  287. begin
  288.     if fCurrentIcon <> 0 then begin
  289.         if not fTransparent then begin
  290.             bm := TBitmap.Create;
  291.             bm.Width := fHeader.dwCX;
  292.             bm.Height := fHeader.dwCY;
  293.             bm.Canvas.Brush.Color := fBackColor;
  294.             bm.Canvas.FillRect (Classes.Rect (0, 0, bm.Width, bm.Height));
  295.             DrawIcon (bm.Canvas.Handle, 0, 0, fCurrentIcon);
  296.             ACanvas.Draw (Rect.Left, Rect.Top, bm);
  297.             bm.Free;
  298.         end else DrawIcon (ACanvas.Handle, Rect.Left, Rect.Top, fCurrentIcon);
  299.     end;
  300. end;
  301.  
  302. procedure TAniIcon.SetFrame (Index: Integer);
  303. type
  304.     TIconHeader = packed record
  305.         AlwaysZero: Word;
  306.         CursorType: Word;
  307.         NumIcons: Word;
  308.     end;
  309.  
  310.     TIconDirEntry = packed record
  311.         Width, Height, Colors: Byte;
  312.         Reserved: Byte;
  313.         dwReserved: LongInt;
  314.         dwBytesInRes: LongInt;
  315.         dwImageOffset: LongInt;
  316.     end;
  317.  
  318. var
  319.     p: PByte;
  320.     ChunkLen: LongInt;
  321.     IconHeader: TIconHeader;
  322. begin
  323.     if (FrameOffsets.Count <> 0) and (Index < fHeader.dwFrames) then begin
  324.        fCurrentFrame := Index;
  325.        // Delete any existing icon
  326.        if fCurrentIcon <> 0 then DestroyIcon (fCurrentIcon);
  327.        // Seek to wanted position in stream data
  328.        Image.Position := Integer (FrameOffsets [Index]);
  329.        Image.ReadBuffer (ChunkLen, sizeof (ChunkLen));
  330.        Image.ReadBuffer (IconHeader, sizeof (IconHeader));
  331.        Image.Position := Image.Position + (sizeof (TIconDirEntry) * IconHeader.NumIcons);
  332.        Dec (ChunkLen, sizeof (IconHeader) + (sizeof (TIconDirEntry) * IconHeader.NumIcons));
  333.  
  334.        p := Image.Memory; Inc (p, Image.Position);
  335.        fCurrentIcon := CreateIconFromResource (p, ChunkLen, True, $30000);
  336.        Changed (Self);
  337.     end;
  338. end;
  339.  
  340. procedure TAniIcon.Animate;
  341. var
  342.     JifRate, NextFrame: Integer;
  343. begin
  344.     if Rates.Count = 0 then JifRate := fHeader.dwJIFRate else JifRate := Integer (Rates [fCurrentStep]);
  345.     Inc (fCurrentJIFs, 4);
  346.     if fCurrentJIFs >= JifRate then begin
  347.         { Time to move on to next step }
  348.         fCurrentJIFs := 0;
  349.         Inc (fCurrentStep);
  350.         if fCurrentStep >= fHeader.dwSteps then fCurrentStep := 0;
  351.         if SequenceMap.Count = 0 then NextFrame := fCurrentFrame + 1 else NextFrame := Integer (SequenceMap [fCurrentStep]);
  352.         if NextFrame >= fHeader.dwFrames then NextFrame := 0;
  353.         if NextFrame <> fCurrentFrame then SetFrame (NextFrame);
  354.     end;
  355. end;
  356.  
  357. end.
  358.